#!/usr/bin/env python
# encoding: utf-8
import re
import os
import sys
import zipfile
import platform
pjoin = os.path.join
rpath = os.path.abspath

name = 'project'
# iot_SDK_720U iot_SDK_720U_BT_TTS iot_SDK_720U_BT_TTS_VOLTE iot_SDK_720U_VOLTE 
AM_MODEL = "iot_SDK_720U_BT_TTS"

CSDK_ROOT = os.getenv('RDA8910_ROOT')
gcc_root = os.path.join(CSDK_ROOT, "prebuilts/win32/gcc-arm-none-eabi")

if not CSDK_ROOT:
  print('RDA8910_ROOT not set.')
  sys.exit(1)

GCC_INSTALL_PATH = os.path.join(gcc_root, 'bin')
SDK_PATH = CSDK_ROOT

# option
AddOption(
  '--vscode',
  dest='vscode',
  action='store_true',
  metavar='EXPORT',
  help='export vscode project settings',
)

PREFIX="arm-none-eabi-"
ARMCC=PREFIX+'gcc'
ARMCXX=PREFIX+'g++'
ARMAS=PREFIX+'gcc'
ARMAR=PREFIX+'ar'
ARMLINK=PREFIX+'g++'
ARMSIZE=PREFIX+'size'
ARMOBJDUMP=PREFIX+'objdump'
ARMOBJCOPY=PREFIX+'objcopy'
INCLUDES = [
  os.path.join(SDK_PATH, 'core/{}/memd'.format(AM_MODEL)),
  os.path.join(SDK_PATH, 'components/include'),
  os.path.join(SDK_PATH, 'components/openat_inc'),
  os.path.join(SDK_PATH, 'components/newlib/include'),
  os.path.join(SDK_PATH, 'components/ril/inc'),
  os.path.join(SDK_PATH, 'api/include'),
]

flags="""
-DCSDK_PRO=CSDK -DCSDK_VER=\"1.0.0\" -D_WINSOCKAPI_
-std=gnu11 -mcpu=cortex-a5 -mtune=generic-armv7-a
-mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mno-unaligned-access
-g -Os -Wall -fno-strict-aliasing -ffunction-sections
-fdata-sections
"""
#-fstrict-aliasing -fomit-frame-pointer -flto -fno-asynchronous-unwind-tables -fno-stack-protector -fno-stack-check
CFLAGS=flags.strip().replace('\n', ' ').split(' ')

#-fstrict-aliasing -fomit-frame-pointer -flto -fno-asynchronous-unwind-tables -fno-stack-protector -fno-stack-check
CXXFLAGS=[el for el in CFLAGS if el!='-std=gnu11']
CXXFLAGS.append([
  '-fno-exceptions',
  '-std=c++11',
  '-fno-rtti',
  '-fno-threadsafe-statics',
])

LINKFLAGS=[
  '-std=gnu11',
  '-Wl,-T$TARGET.dir/app_flashimg.lds',
  '-Wl,-Map=$TARGET.dir/output.map',
  '-nostdlib',
  '-Wl,--gc-sections',
]

DEFINES = []

LIBS=[
  File(os.path.abspath(os.path.join(SDK_PATH, 'components/newlib/armca5/libc.a'))),
  File(os.path.abspath(os.path.join(SDK_PATH, 'components/newlib/armca5/libm.a'))),
  File(os.path.abspath(os.path.join(SDK_PATH, 'components/newlib/armca5/libc.a'))),
  File(os.path.abspath(os.path.join(GCC_INSTALL_PATH, '../lib/gcc/arm-none-eabi/7.2.1/thumb/v7-ar/fpv3/hard/libgcc.a'))),
]

env=Environment(
  AS=ARMAS,
  CC=ARMCC,
  CXX=ARMCXX,
  AR=ARMAR,
  LINK=ARMLINK,
  CFLAGS=CFLAGS,
  CXXFLAGS=CXXFLAGS,
  CCCOMSTR='CC $TARGET',
  CXXCOMSTR='CXX $TARGET',
  LINKCOMSTR='LINK $TARGET',
  ARCOMSTR='AR $TARGET',
  LINKFLAGS=LINKFLAGS,
  CPPDEFINES=DEFINES,
  CPPPATH=INCLUDES,
  LIBS=LIBS,
  TOOLS=["mingw"]
)
env.PrependENVPath('PATH', GCC_INSTALL_PATH)
env.PrependENVPath('PATH', os.path.abspath(os.path.join(SDK_PATH, 'tools/win32')))
env.PrependENVPath('PATH', os.path.abspath(os.path.join(SDK_PATH, 'prebuilts/win32/python3')))

import json
import stat
def check_vscode_dir(d):
  try:
    res = os.stat(d)
    if not stat.S_ISDIR(res.st_mode):
      msg = '%s is not a dir, please remove it first.' % d
      print(d)
      raise Exception(d)
  except Exception as e:
    os.mkdir(d)

def vscode_project_action(source, target, env):
  args = {
    'name': name,
    'intelliSenseMode': 'gcc-arm',
    'includePath': INCLUDES,
    'defines': [],
    'forcedInclude': [],
    'cStandard': 'c99',
    'cppStandard': 'c++11',
    'compilerPath': rpath(pjoin(GCC_INSTALL_PATH, ARMCXX + '.exe')),
    # 'compileCommands': 'scons',
    'browse': {
      'path': [],
      'limitSymbolsToIncludedHeaders': True,
      'databaseFilename': '',
    }
  }
  out = {
    'env': {},
    'configurations': [args],
    'version': 4,
  }
  check_vscode_dir('.vscode')
  with open('.vscode/c_cpp_properties.json', 'wt') as fout:
    fout.write(json.dumps(out, indent=4))
  print('vscode project generate completed.')
env['BUILDERS']['Vscode'] = Builder(action=vscode_project_action)

dtools = 'dtools.exe'
python3 = 'python3.exe'
pacgen = os.path.join(SDK_PATH, 'tools/pacgen.py')

# 执行8910固件dump过程
# iot_SDK_720U_BT_TTS => _BT_TTS
img_suffix = AM_MODEL[len('iot_SDK_720U'):]
pac_dump_action='%s pacdump $SOURCES $TARGET' % dtools
pac_dir_path = os.path.join(SDK_PATH, 'core/{}'.format(AM_MODEL))
pac_file = os.path.join(pac_dir_path, 'CSDK_RDA8910{}.pac'.format(img_suffix))
dumped_pac = env.Command(target='build/out/oldpac', source=pac_file, action=pac_dump_action, target_factory=Dir)

link_file_cmd = '{}  {} -E -P -x c -I {} -o $TARGET -c $SOURCE'.format(ARMCC, ' '.join(CFLAGS), os.path.join(SDK_PATH, 'core/{}/memd'.format(AM_MODEL)))
env['BUILDERS']['LinkScript'] = Builder(action=link_file_cmd, suffix=".lds", src_suffix='.ld')

link_source_file = os.path.join(SDK_PATH, 'ldscripts/app_flashimg.ld')
link_ld_file = 'build/app_flashimg.lds'
link_result = env.LinkScript(target=link_ld_file, source=link_source_file)
env.Depends(link_result, dumped_pac)

# VariantDir('build')
Export('env')
Export('SDK_PATH')
Export('CSDK_ROOT')
Export('INCLUDES')

lib_app = env.SConscript('app/Sconstruct', variant_dir='build/app')

lib_sdk = env.SConscript('sdk.scons', variant_dir='build/sdk', duplicate=0)
core_stub = File(os.path.join(SDK_PATH, 'core/{}/lib/core_stub.o'.format(AM_MODEL)))

if GetOption('vscode') == True:
  env.Vscode(source=[], target='.vscode/c_cpp_properties.json')

def parse_cmake_set_info(f_path):
  out = {}
  lines = []
  with open(f_path, 'rt') as f_in:
    lines = f_in.readlines()
  lines = [line.strip() for line in lines]
  lines = [line for line in lines if not line.startswith('#')]
  lines = [line for line in lines if line.startswith('set')]
  lines = [line for line in lines if len(line) > 1]
  r1 = re.compile(r'set\((.*?) (.*?)\)')
  for line in lines:
    gs = r1.match(line)
    key = gs.group(1)
    v = gs.group(2)
    out[key] = v
  return out

# 解析 version, partinfo, core
board_vars = {
  'pacgen': pacgen,
  'build_target': name,
  'sdk_path': SDK_PATH,
  'img_suffix': img_suffix,
}
version_vars = parse_cmake_set_info(os.path.join(SDK_PATH, "core", AM_MODEL, "memd/version.cmake"))
partinfo_vars = parse_cmake_set_info(os.path.join(SDK_PATH, "core", AM_MODEL, "memd/partinfo.cmake"))
core_config_vars = parse_cmake_set_info(os.path.join(SDK_PATH, "core", AM_MODEL, "memd/core_config.cmake"))
board_vars.update(version_vars)
board_vars.update(partinfo_vars)
board_vars.update(core_config_vars)
# print(board_vars)

# 1. pacgen appimg_flash_delete.pac
c  = ' python3 {pacgen}'
c += ' cfg-init --pname UIX8910_MODEM --palias {build_target} --pversion "8910 MODULE" --version BP_R1.0.0 --flashtype 1'
c += ' cfg-host-fdl -a {CONFIG_FDL1_IMAGE_START} -s {CONFIG_FDL1_IMAGE_SIZE} -p $SOURCE/fdl1.img'
c += ' cfg-fdl2 -a {CONFIG_FDL2_IMAGE_START} -s {CONFIG_FDL2_IMAGE_SIZE} -p $SOURCE/fdl2.img'
c += ' cfg-erase-flash -i ERASE_APPIMG -a {CONFIG_APPIMG_FLASH_ADDRESS} -s {CONFIG_APPIMG_FLASH_SIZE}'
c += ' pac-gen $TARGET'
env['BUILDERS']['GenDeletePac'] = Builder(action=c.format(**board_vars))
env.GenDeletePac(target='build/appimg_flash_delete.pac', source=dumped_pac)

# 2. compile elf
elftarget='build/%s.elf' % name
elf_result = env.Program(source=[core_stub, lib_app, lib_sdk], target=elftarget)
env.Depends(elf_result, link_result)

# 3. mkappimg
mkappimg = '%s mkappimg $SOURCES $TARGET' % dtools
env['BUILDERS']['MkAppImg'] = Builder(action=mkappimg, suffix=".img", src_suffix='.elf')
imgtarget = 'build/%s.img' % name
env.MkAppImg(source=elftarget, target=imgtarget)

# 4. generate app pac
c  = ' python3 {pacgen}'
c += ' cfg-init --pname UIX8910_MODEM --palias {build_target} --pversion "8910 MODULE" --version BP_R1.0.0 --flashtype 1'
c += ' cfg-host-fdl -a {CONFIG_FDL1_IMAGE_START} -s {CONFIG_FDL1_IMAGE_SIZE} -p build/out/oldpac/fdl1.img'
c += ' cfg-fdl2 -a {CONFIG_FDL2_IMAGE_START} -s {CONFIG_FDL2_IMAGE_SIZE} -p build/out/oldpac/fdl2.img'
c += ' cfg-nvitem -n Calibration -i 0xFFFFFFFF --use 1 --replace 0 --continue 0 --backup 1'
c += ' cfg-nvitem -n "GSM Calibration" -i 0x26d --use 1 --replace 0 --continue 1 --backup 1'
c += ' cfg-nvitem -n "LTE Calibration" -i 0x26e --use 1 --replace 0 --continue 0 --backup 1'
c += ' cfg-nvitem -n IMEI -i 0xFFFFFFFF --use 1 --replace 0 --continue 0 --backup 1'
c += ' cfg-image -i APPIMG -a {CONFIG_APPIMG_FLASH_ADDRESS} -s {CONFIG_APPIMG_FLASH_SIZE} -p $SOURCE'
c += ' dep-gen --base {sdk_path} $TARGET $TARGET.dir/app.pac.d'
c += ' pac-gen $TARGET'
app_pac_filename = 'build/Air720U_CSDK_%s_APP.pac' % name
env['BUILDERS']['GenAppPac'] = Builder(action=c.format(**board_vars))
env.GenAppPac(target=app_pac_filename, source=imgtarget)

# 5. generate whole pac
c  = ' python3 {pacgen}'
c += ' cfg-init --pname UIX8910_MODEM --palias {build_target} --pversion "8910 MODULE" --version BP_R1.0.0 --flashtype 1'
c += ' cfg-host-fdl -a {CONFIG_FDL1_IMAGE_START} -s {CONFIG_FDL1_IMAGE_SIZE} -p build/out/oldpac/fdl1.img'
c += ' cfg-fdl2 -a {CONFIG_FDL2_IMAGE_START} -s {CONFIG_FDL2_IMAGE_SIZE} -p build/out/oldpac/fdl2.img'
c += ' cfg-nvitem -n Calibration -i 0xFFFFFFFF --use 1 --replace 0 --continue 0 --backup 1'
c += ' cfg-nvitem -n "GSM Calibration" -i 0x26d --use 1 --replace 0 --continue 1 --backup 1'
c += ' cfg-nvitem -n "LTE Calibration" -i 0x26e --use 1 --replace 0 --continue 0 --backup 1'
c += ' cfg-nvitem -n IMEI -i 0xFFFFFFFF --use 1 --replace 0 --continue 0 --backup 1'
c += ' cfg-phase-check cfg-image -i BOOTLOADER -a 0x60000000 -s 0x10000 -p build/out/oldpac/boot.img'
c += ' cfg-image -i AP -a {CONFIG_APP_FLASH_ADDRESS} -s {CONFIG_APP_FLASH_SIZE} -p build/out/oldpac/8915DM_cat1_CSDK{img_suffix}.img'
c += ' cfg-image -i APPIMG -a {CONFIG_APPIMG_FLASH_ADDRESS} -s {CONFIG_APPIMG_FLASH_SIZE} -p $SOURCE'
c += ' cfg-image -i PS -a {CONFIG_FS_MODEM_FLASH_ADDRESS} -s {CONFIG_FS_MODEM_FLASH_SIZE} -p build/out/oldpac/cat1_UIS8915DM_BB_RF_SS_NoVolte_cus.img'
c += ' cfg-fmt-flash -i FMT_FSSYS -b FSYS'
c += ' cfg-clear-nv'
c += ' cfg-nv -s 0x20000 -p build/out/oldpac/cat1_UIS8915DM_BB_RF_SS_NoVolte_cus_nvitem.bin'
c += ' dep-gen --base {sdk_path} $TARGET $TARGET.dir/all.pac.d'
c += ' pac-gen $TARGET'
all_pac_filename = 'build/Air720U_CSDK_%s.pac' % name
env['BUILDERS']['GenAppPac'] = Builder(action=c.format(**board_vars))
env.GenAppPac(target=all_pac_filename, source=imgtarget)

# 6. generate ota bin
# copy Luat_CSDK_Air724U/core/{am_model}/CSDK_RDA8910.pac to CSDK_RDA8910.pac
rda8910_bin = env.Command('build/CSDK_RDA8910.bin', pac_file, Copy('$TARGET', '$SOURCE'))
app_bin = env.Command('build/app.bin', imgtarget, Copy('$TARGET', '$SOURCE'))

def zip_file_action(source, target, env):
  target_file = target[0]
  f = zipfile.ZipFile(target_file.get_path(), 'w', zipfile.ZIP_DEFLATED)
  for item in source:
    with open(item.get_path(), 'rb') as f_in:
      f.writestr(os.path.basename(item.get_path()), f_in.read())
  print('fota bin file generate succeed.')

# make fota bin
env['BUILDERS']['ZipFile'] = Builder(action=zip_file_action)
env.ZipFile('build/project.bin', [rda8910_bin, app_bin])

# make app ota firmware.
import struct
def ota_firmware(source, target, env):
  file_count = 1
  with open(source[0].get_path(), 'rb') as f_in:
    content = f_in.read()
    with open(target[0].get_path(), 'wb') as out:
      out.write(b'\x7E')
      out.write(struct.pack(b'>I', file_count))
      out.write(b'\x7C')
      out.write(struct.pack(b'>I', len(content)))
      out.write(content)

env['BUILDERS']['AppOta'] = Builder(action=ota_firmware)
env.AppOta('build/project.app.ota.bin', app_bin)
